﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSPComm.SSPCommands;

namespace SSPComm.SSPReplies
{
    internal static class SSPReplyParser
    {
        public static SSPReply Parse(SSPData inData)
        {
            Byte[] data = inData.Data.ToArray();
			if (data.Length == 0)
				throw new ArgumentException("Input array is null");

            SSPReply result = new SSPReply(data);
            if (data.Length == 1)
            {
                return new SSPSimpleReply(data);
            }

            #region OK
            if (data[0] == (Byte)SSPDataTypes.OK)
            {
                List<SSPSmartHopperPollEvents> checkOperations = new List<SSPSmartHopperPollEvents>(){
                    SSPSmartHopperPollEvents.Dispensing,
                    SSPSmartHopperPollEvents.Dispensed,
                    SSPSmartHopperPollEvents.Jammed,
                    SSPSmartHopperPollEvents.Halted,
                    SSPSmartHopperPollEvents.Floated,
                    SSPSmartHopperPollEvents.Floating,
                    SSPSmartHopperPollEvents.TimeOut,
                    SSPSmartHopperPollEvents.CashBoxPaid,
                    SSPSmartHopperPollEvents.FraudAttempt,
                    SSPSmartHopperPollEvents.SmartEmptying,
                    SSPSmartHopperPollEvents.SmartEmptied};

                List<SSPSmartHopperPollEvents> checkSingle = new List<SSPSmartHopperPollEvents>(){
                    SSPSmartHopperPollEvents.LidOpen,
                    SSPSmartHopperPollEvents.LidClosed,
                    SSPSmartHopperPollEvents.Jammed,
                    SSPSmartHopperPollEvents.Emptying,
                    SSPSmartHopperPollEvents.Empty,
                    SSPSmartHopperPollEvents.CoinMechJammed,
                    SSPSmartHopperPollEvents.CoinMechReturnPressed,
                    SSPSmartHopperPollEvents.LowPayoutLevel,
                    SSPSmartHopperPollEvents.Disabled,
                    SSPSmartHopperPollEvents.SlaveReset};

                //TODO refactor
				#region 0xF0 0xF1 0xE8 ...
				if ((data.Length > 2) && (data[1] == 0xF1) && (data[2] == 0xE8))
				{
					Byte[] dispData = new Byte[data.Length - 2];
					dispData[0] = 0xF0;
					// for data.Length == 3 no copying is done
					Array.Copy(data, 3, dispData, 1, dispData.Length - 1);
					data = dispData;

					if (data.Length == 1)
					{
						return new SSPSimpleReply(data);
					}
				}
				#endregion
				
                #region 0xF0 0xE8 ...
                if ((data[1] == 0xE8) && (data.Length > 2))
                {
                    Byte[] dispData = new Byte[data.Length - 1];
                    dispData[0] = 0xF0;
                    Array.Copy(data, 2, dispData, 1, dispData.Length - 1);
                    data = dispData;

                    if (data.Length == 1)
                    {
                        return new SSPSimpleReply(data);
                    }
                }
                #endregion

                Int32 counter = 0;
                SSPMoneyContext currCoin;
                List<SSPMoneyContext> coinsList;
                SSPSmartHopperPollEvents currTypeEvent;
                #region Operations
                if (checkOperations.Contains((SSPSmartHopperPollEvents)data[counter + 1]))
                {
                    List<SSPCoinsOperation> operList = new List<SSPCoinsOperation>();
                    while (checkOperations.Contains((SSPSmartHopperPollEvents)data[counter += 1]))
                    {
                        currTypeEvent = (SSPSmartHopperPollEvents)data[counter];
                        // 0    1    2    3    4    5    6    7    8
                        //0xDE 0x02 0x00 0x00 0x00 0x00 0x52 0x55 0x42 0xD2 0x01 0xC8 0x00 0x00 0x00 0x52 0x55 0x42
                        Int32 currCount = data[counter += 1];
                        coinsList = new List<SSPMoneyContext>(currCount);
                        int i;
                        for (i = 0; i < currCount; i++)
                        {
                            currCoin = new SSPMoneyContext(BitConverter.ToUInt32(data, counter += 1), new List<Byte>(data).GetRange(counter += 4, 3).ToArray());
                            counter += 2;
                            coinsList.Add(currCoin);
                        }
                        operList.Add(new SSPCoinsOperation(currTypeEvent, coinsList.ToArray()));
                        if (data.Length - 1 < counter + 1)
                        {
                            break;
                        }
                    }
                    result = new SSPCoinsReply(operList, data);
                }
                #endregion
                else if (checkSingle.Contains((SSPSmartHopperPollEvents)data[counter + 1]))
                {
                    result = new SSPPollReply((SSPSmartHopperPollEvents)data[counter + 1], data);
                }
                else if (data[counter + 1] == (Byte)SSPSmartHopperPollEvents.CalibrationFail)
                {
                    result = new SSPCalibrationReply((SSPSmartHopperCalibErr)data[counter + 2], data);
                }
                else if (data[counter + 1] == (Byte)SSPSmartHopperPollEvents.CoinCredit)
                {
                    currCoin = new SSPMoneyContext(BitConverter.ToUInt32(data, counter += 2),
                        new List<Byte>(data).GetRange(counter += 4, 3).ToArray());
                    result = new SSPCoinsReply(new List<SSPCoinsOperation>(1) 
                                                { new SSPCoinsOperation(SSPSmartHopperPollEvents.CoinCredit, 
                                                        new SSPMoneyContext[] { currCoin }) }, data);
                }
                else
                {
                    result = new SSPReply(data);
                }
            }
            #endregion
            #region 0xF5
            else if (data[0] == 0xF5)
            {
                result = new SSPPayoutReply((SSPPayoutErr)data[1], data);
            }
            #endregion
            return result;

        }

        public static SSPSetupReply ParseSetup(SSPData inData)
        {
            if (!inData.IsOK)
            {
                throw new SSPException(inData, "First byte isn't 0xF0");
            }
            List<Byte> data = new List<Byte>(inData.Data);
            Byte unitType = data[1];
            Byte[] firmware = data.GetRange(2, 4).ToArray();
            Byte[] country = data.GetRange(6, 3).ToArray();
            Byte protocol = data[9];
            Byte coinsCount = data[10];
            List<SSPMoneyContext> listContext = new List<SSPMoneyContext>(coinsCount);

            Int32 offsetCoins = 11;
            Int32 offsetCountry = offsetCoins + 2*coinsCount;

            UInt16 currValue;
            Byte[] currCountry;
            for (int i = 0; i < coinsCount; i++)
            {
                currValue = BitConverter.ToUInt16(data.ToArray(), i * 2 + offsetCoins);
                currCountry = data.GetRange(offsetCountry + i * 3, 3).ToArray();
                listContext.Add(new SSPMoneyContext(currValue, currCountry));
            }
            return new SSPSetupReply(unitType, firmware, country, protocol, listContext, inData.Data);
        }

        public static SSPAmountReply ParseAmount(SSPData inData)
        {
            if (!inData.IsOK)
            {
                throw new SSPException(inData, "First byte isn't 0xF0");
            }

            UInt16 value = BitConverter.ToUInt16(inData.Data, 1);
            return new SSPAmountReply(value, inData.Data);
        }

        public static SSPSetRoutingReply ParseRouting(SSPData inData)
        {
            if (inData.IsOK)
            {
                SSPRoute route = (SSPRoute)inData.Data[1];
                return new SSPSetRoutingReply(route, inData.Data);   
            }
            else
            {
                throw new SSPException(inData, "First byte err");
            }
        }

        public static SSPPayoutReply ParsePayout(SSPData inData)
        {
            List<SSPDataTypes> checkList = new List<SSPDataTypes>{
                                                    SSPDataTypes.OK,
                                                    SSPDataTypes.Unknown,
                                                    SSPDataTypes.ParamCountErr,
                                                    SSPDataTypes.ParamValErr,
                                                    SSPDataTypes.SoftErr,
                                                    SSPDataTypes.Fail,
                                                    SSPDataTypes.KeyNotSet};
            if (checkList.Contains((SSPDataTypes)inData.Data[0]))
            {
                return new SSPPayoutReply(SSPPayoutErr.None, inData.Data);
            }
            else if ((SSPDataTypes)inData.Data[0] == SSPDataTypes.CmdErr)
            {
                return new SSPPayoutReply((SSPPayoutErr)inData.Data[1], inData.Data);
            }
            else
            {
                throw new SSPException(inData, "First byte err");
            }
        }

        public static SSPGetSerialReply ParseSerial(SSPData inData)
        {
            if ((inData.IsOK) && (inData.Data.Length == 5))
            {
                List<Byte> serialList = new List<Byte>(4);
                serialList.AddRange(inData.Data);
                serialList.Reverse();
                UInt32 serial = BitConverter.ToUInt32(serialList.ToArray(), 0);
                return new SSPGetSerialReply(serial, inData.Data);
            }
            else
            {
                throw new SSPException(inData, "Parse err");
            }

        }
    }

#if DEBUG
    namespace Testing
    {
        using NUnit.Framework;
        [TestFixture]
        public class SSPReplyParserTest
        {
            [Test]
            public void SSPPollDataTest()
            {
                Byte[] data = new Byte[] { 0xF0, 0xDE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x52, 0x55, 0x42, 
                                                             0x0A, 0x00, 0x00, 0x00, 0x52, 0x55, 0x42,  
                                                 0xD2, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x52, 0x55, 0x42};

                SSPReply pollReply = SSPReplyParser.Parse(new SSPData(data));

                Assert.AreEqual(typeof(SSPCoinsReply), pollReply.GetType());
                Assert.AreEqual(2, ((SSPCoinsReply)pollReply).Operations.Length);
                Assert.AreEqual(2, ((SSPCoinsReply)pollReply).Operations[0].Coins.Length);
                Assert.AreEqual(SSPSmartHopperPollEvents.CashBoxPaid, ((SSPCoinsReply)pollReply).Operations[0].Event);
                Assert.AreEqual(0, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].Value);
                Assert.AreEqual(0x0A, ((SSPCoinsReply)pollReply).Operations[0].Coins[1].Value);
                Assert.AreEqual(SSPSmartHopperPollEvents.Dispensed, ((SSPCoinsReply)pollReply).Operations[1].Event);
                Assert.AreEqual(0xC8, ((SSPCoinsReply)pollReply).Operations[1].Coins[0].Value);


                data = new Byte[] { 0xF0, 0x81 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(SSPSmartHopperPollEvents.LidOpen, ((SSPPollReply)pollReply).Event);
                Assert.AreEqual(data, pollReply.RawData);

                data = new Byte[] { 0xF0, 0xC3 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(SSPSmartHopperPollEvents.Empty, ((SSPPollReply)pollReply).Event);

                data = new Byte[] { 0xF5, 0x03 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(SSPPayoutErr.HopperBusy, ((SSPPayoutReply)pollReply).Error);

                data = new Byte[] { 0xF0, 0xDF, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations.Length);
                Assert.AreEqual(SSPSmartHopperPollEvents.CoinCredit, ((SSPCoinsReply)pollReply).Operations[0].Event);
                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations[0].Coins.Length);
                Assert.AreEqual(100, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].Value);
                Assert.AreEqual(new Byte[] { 0x45, 0x55, 0x52 }, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].CountryCode);


                data = new Byte[] { 0xF0, 0xDA, 0x01, 0xF4, 0x01, 0x00, 0x00, 0x52, 0x55, 0x42 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(typeof(SSPCoinsReply), pollReply.GetType());
                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations.Length);
                Assert.AreEqual(SSPSmartHopperPollEvents.Dispensing, ((SSPCoinsReply)pollReply).Operations[0].Event);
                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations[0].Coins.Length);
                Assert.AreEqual(500, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].CountryCode);

                data = new Byte[] { 0xF0, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x52, 0x55, 0x42, 0xD2, 0x01, 0xF4, 0x01, 0x00, 0x00, 0x52, 0x55, 0x42 };
                pollReply = SSPReplyParser.Parse(new SSPData(data));
                Assert.AreEqual(typeof(SSPCoinsReply), pollReply.GetType());
                Assert.AreEqual(2, ((SSPCoinsReply)pollReply).Operations.Length);
                Assert.AreEqual(SSPSmartHopperPollEvents.CashBoxPaid, ((SSPCoinsReply)pollReply).Operations[0].Event);
                Assert.AreEqual(SSPSmartHopperPollEvents.Dispensed, ((SSPCoinsReply)pollReply).Operations[1].Event);
                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations[0].Coins.Length);
                Assert.AreEqual(0, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, ((SSPCoinsReply)pollReply).Operations[0].Coins[0].CountryCode);

                Assert.AreEqual(1, ((SSPCoinsReply)pollReply).Operations[1].Coins.Length);
                Assert.AreEqual(500, ((SSPCoinsReply)pollReply).Operations[1].Coins[0].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, ((SSPCoinsReply)pollReply).Operations[1].Coins[0].CountryCode);


                data = new Byte[]{  0xF0, 
                                    0x03, 
                                    0x30, 0x36, 0x30, 0x34, 
                                    0x52, 0x55, 0x42, 
                                    0x06, 
                                    0x06, 
                                    0x0A, 0x00, 
                                    0x32, 0x00, 
                                    0x64, 0x00, 
                                    0xC8, 0x00, 
                                    0xF4, 0x01, 
                                    0xE8, 0x03, 
                                    0x52, 0x55, 0x42, 
                                    0x52, 0x55, 0x42, 
                                    0x52, 0x55, 0x42, 
                                    0x52, 0x55, 0x42, 
                                    0x52, 0x55, 0x42, 
                                    0x52, 0x55, 0x42};
                SSPSetupReply setup = SSPReplyParser.ParseSetup(new SSPData(data));
                Assert.AreEqual(0x03, setup.UnitType);
                Assert.AreEqual(new Byte[] { 0x30, 0x36, 0x30, 0x34 }, setup.Firmware);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.MainCountry);
                Assert.AreEqual(0x06, setup.Protocol);
                Assert.AreEqual(0x06, setup.Coins.Length);

                Assert.AreEqual(10, setup.Coins[0].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[0].CountryCode);
                Assert.AreEqual(50, setup.Coins[1].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[1].CountryCode);
                Assert.AreEqual(100, setup.Coins[2].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[2].CountryCode);
                Assert.AreEqual(200, setup.Coins[3].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[3].CountryCode);
                Assert.AreEqual(500, setup.Coins[4].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[4].CountryCode);
                Assert.AreEqual(1000, setup.Coins[5].Value);
                Assert.AreEqual(new Byte[] { 0x52, 0x55, 0x42 }, setup.Coins[5].CountryCode);


				data = new Byte[] { 0xF0, 0x7D, 0xBC, 0xEC, 0x05, 0x00, 0x00, 0x00, 0x00 };
				pollReply = SSPReplyParser.Parse(new SSPData(data));
				Assert.AreEqual(data, pollReply.RawData);

				data = new Byte[] {0xF0, 0x2C, 0x01};
				pollReply = SSPReplyParser.Parse(new SSPData(data));
				Assert.AreEqual(data, pollReply.RawData);
				
				data = new Byte[] {0xF0, 0xFF};
				pollReply = SSPReplyParser.Parse(new SSPData(data));
				Assert.AreEqual(data, pollReply.RawData);

				data = new Byte[] {0xF0, 0xF1};
				pollReply = SSPReplyParser.Parse(new SSPData(data));
				Assert.AreEqual(SSPSmartHopperPollEvents.SlaveReset, ((SSPPollReply)pollReply).Event);
				Assert.AreEqual(data, pollReply.RawData);
            }
        }
    }
#endif
}
